using System;
using System.ComponentModel;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using ModelContextProtocol.AspNetCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using ModelContextProtocol.Server;

namespace WeatherAlertMcp.Api.Tools
{
    // 标注为 MCP 工具类型
    [McpServerToolType]
    public class WeatherTool
    {
        private readonly HttpClient _httpClient;
        private readonly IMemoryCache _cache;
        private readonly ILogger<WeatherTool> _logger;

        // 优先从环境变量读取 API Key（例如 WEATHER_API_KEY）；否则回退到占位符
        private static string ApiKey =>
            Environment.GetEnvironmentVariable("WEATHER_API_KEY") ?? "YOUR_API_KEY";
        private static bool HasWeatherApiKey =>
            !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("WEATHER_API_KEY"))
            && Environment.GetEnvironmentVariable("WEATHER_API_KEY") != "YOUR_API_KEY";

        // 和风天气（QWeather）优先
        private static string? QWeatherKey => Environment.GetEnvironmentVariable("QWEATHER_API_KEY");
        private static bool HasQWeatherKey => !string.IsNullOrWhiteSpace(QWeatherKey);

        public WeatherTool(IHttpClientFactory httpClientFactory, IMemoryCache cache, ILogger<WeatherTool> logger)
        {
            _httpClient = httpClientFactory.CreateClient();
            _cache = cache;
            _logger = logger;
        }

        // 暴露为 MCP 工具：单次预报查询
        [McpServerTool(Name = "get-forecast")]
        [Description("获取指定坐标的天气预报")]
        public async Task<string> GetForecastAsync(
            [Description("纬度")] double latitude,
            [Description("经度")] double longitude)
        {
            try
            {
                if (HasQWeatherKey)
                {
                    // QWeather 需要 location=lon,lat
                    var location = $"{longitude:F6},{latitude:F6}";
                    var nowUrl = $"https://api.qweather.com/v7/weather/now?location={location}&key={QWeatherKey}";
                    var forecastUrl = $"https://api.qweather.com/v7/weather/3d?location={location}&key={QWeatherKey}";

                    var nowTask = _httpClient.GetFromJsonAsync<QwNowResponse>(nowUrl);
                    var fcTask = _httpClient.GetFromJsonAsync<Qw3dResponse>(forecastUrl);
                    await Task.WhenAll(nowTask!, fcTask!);

                    var now = nowTask!.Result;
                    var fc = fcTask!.Result;
                    if (now is null || fc is null || now.Code != "200" || fc.Code != "200")
                    {
                        return "无法获取天气数据";
                    }
                    return "数据源：和风天气(QWeather)\n" + FormatForecastFromQWeather(now, fc);
                }
                else if (HasWeatherApiKey)
                {
                    // 使用 WeatherAPI（需要密钥）
                    var url = $"https://api.weatherapi.com/v1/forecast.json?key={ApiKey}&q={latitude},{longitude}&days=3";
                    var response = await _httpClient.GetFromJsonAsync<WeatherData>(url);
                    if (response is null) return "无法获取天气数据";
                    return "数据源：WeatherAPI.com\n" + FormatForecast(response);
                }
                else
                {
                    // 无密钥降级：使用 Open-Meteo 免费接口
                    var url = $"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&current=temperature_2m&daily=temperature_2m_max,temperature_2m_min&forecast_days=3&timezone=auto";
                    var om = await _httpClient.GetFromJsonAsync<OpenMeteoResponse>(url);
                    if (om is null) return "无法获取天气数据";
                    return "数据源：Open-Meteo\n" + FormatForecastFromOpenMeteo(om);
                }
            }
            catch (HttpRequestException ex)
            {
                _logger.LogWarning(ex, "天气 API 请求失败");
                return "无法获取天气数据";
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "天气工具出现异常");
                return "服务暂时不可用";
            }
        }

        // 暴露为 MCP 工具：批量查询
        [McpServerTool(Name = "batch-query")]
        [Description("批量获取多个坐标的三日天气预报，按顺序返回合并文本")]
        public async Task<string> BatchQueryAsync(
            [Description("坐标列表，元素包含纬度(lat)与经度(lon)")] IEnumerable<Coordinate> coordinates)
        {
            var tasks = coordinates.Select(c => GetForecastAsync(c.Lat, c.Lon));
            var results = await Task.WhenAll(tasks);
            var sb = new StringBuilder();
            int i = 1;
            foreach (var r in results)
            {
                sb.AppendLine($"[{i}] {r}");
                i++;
            }
            return sb.ToString();
        }

        // 暴露为 MCP 工具：带缓存查询（10 分钟）
        [McpServerTool(Name = "get-cached-forecast")]
        [Description("获取指定坐标的天气预报（含 10 分钟内存缓存）")]
        public async Task<string> GetCachedForecastAsync(
            [Description("纬度")] double latitude,
            [Description("经度")] double longitude)
        {
            var provider = HasQWeatherKey ? "qw" : (HasWeatherApiKey ? "wa" : "om");
            var cacheKey = $"forecast:{latitude:F4},{longitude:F4}:{provider}";
            return await _cache.GetOrCreateAsync(cacheKey, async entry =>
            {
                entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
                return await GetForecastAsync(latitude, longitude);
            }) ?? "无法获取天气数据";
        }

        // 暴露为 MCP 工具：中文地址 -> 天气
        [McpServerTool(Name = "get-forecast-by-address")]
        [Description("通过中文地址解析坐标并获取三日天气预报（需配置 BAIDU_MAPS_AK）")]
        public async Task<string> GetForecastByAddressAsync([Description("中文地址")] string address)
        {
            if (string.IsNullOrWhiteSpace(address)) return "地址不能为空";
            var ak = Environment.GetEnvironmentVariable("BAIDU_MAPS_AK");
            if (string.IsNullOrWhiteSpace(ak)) return "未配置 BAIDU_MAPS_AK";

            try
            {
                var url = $"https://api.map.baidu.com/geocoding/v3/?address={Uri.EscapeDataString(address)}&output=json&ak={ak}";
                var geo = await _httpClient.GetFromJsonAsync<BaiduGeoResponse>(url);
                if (geo is null || geo.Status != 0 || geo.Result?.Location is null)
                {
                    return $"地址解析失败：{address}";
                }

                // Baidu 返回 BD-09，经由 GCJ-02 转到 WGS84，再查询
                var bdLat = geo.Result.Location.Lat;
                var bdLon = geo.Result.Location.Lng;
                var (gcjLat, gcjLon) = Bd09ToGcj02(bdLat, bdLon);
                var (wgsLat, wgsLon) = Gcj02ToWgs84(gcjLat, gcjLon);
                var body = await GetForecastAsync(wgsLat, wgsLon);
                var header = $"查询地址：{address}\n解析坐标(WGS84)：{wgsLat:F6},{wgsLon:F6}\n";
                return header + body;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "地址查询失败");
                return $"地址查询失败：{address}";
            }
        }

        private static string FormatForecast(WeatherData data)
        {
            var sb = new StringBuilder();
            sb.AppendLine($"当前温度：{data.Current.TempC}°C");
            sb.AppendLine("未来三天预报：");

            foreach (var day in data.Forecast.Forecastday.Take(3))
            {
                sb.AppendLine($"{day.Date:yyyy-MM-dd} | 最高 {day.Day.MaxTempC}°C 最低 {day.Day.MinTempC}°C");
            }
            return sb.ToString();
        }

        private static string FormatForecastFromOpenMeteo(OpenMeteoResponse om)
        {
            var sb = new StringBuilder();
            sb.AppendLine($"当前温度：{om.Current?.TemperatureC ?? 0}°C");
            sb.AppendLine("未来三天预报：");
            if (om.Daily?.Time != null && om.Daily.TMax != null && om.Daily.TMin != null)
            {
                for (int i = 0; i < Math.Min(3, om.Daily.Time.Length); i++)
                {
                    var dateStr = om.Daily.Time[i];
                    if (!DateTime.TryParse(dateStr, out var dt)) dt = DateTime.Today.AddDays(i);
                    var max = om.Daily.TMax.Length > i ? om.Daily.TMax[i] : 0;
                    var min = om.Daily.TMin.Length > i ? om.Daily.TMin[i] : 0;
                    sb.AppendLine($"{dt:yyyy-MM-dd} | 最高 {max}°C 最低 {min}°C");
                }
            }
            return sb.ToString();
        }

        private static string FormatForecastFromQWeather(QwNowResponse now, Qw3dResponse fc)
        {
            var sb = new StringBuilder();
            if (!string.IsNullOrEmpty(now.Now?.ObsTime))
                sb.AppendLine($"观测时间：{now.Now!.ObsTime}");
            var temp = now.Now?.Temp;
            sb.AppendLine($"当前温度：{temp ?? "-"}°C");
            sb.AppendLine("未来三天预报：");
            if (fc.Daily != null)
            {
                foreach (var d in fc.Daily.Take(3))
                {
                    sb.AppendLine($"{d.FxDate} | 最高 {d.TempMax}°C 最低 {d.TempMin}°C");
                }
            }
            return sb.ToString();
        }

        // ================= 坐标转换与百度响应模型 =================
        private static (double lat, double lon) Bd09ToGcj02(double bdLat, double bdLon)
        {
            double x = bdLon - 0.0065;
            double y = bdLat - 0.006;
            double z = Math.Sqrt(x * x + y * y) - 0.00002 * Math.Sin(y * Math.PI * 3000.0 / 180.0);
            double theta = Math.Atan2(y, x) - 0.000003 * Math.Cos(x * Math.PI * 3000.0 / 180.0);
            double gcjLon = z * Math.Cos(theta);
            double gcjLat = z * Math.Sin(theta);
            return (gcjLat, gcjLon);
        }

        private static (double lat, double lon) Gcj02ToWgs84(double gcjLat, double gcjLon)
        {
            // 粗略反算，已足够天气查询精度
            var (dLat, dLon) = Delta(gcjLat, gcjLon);
            return (gcjLat - dLat, gcjLon - dLon);
        }

        private static (double dLat, double dLon) Delta(double lat, double lon)
        {
            const double a = 6378245.0;
            const double ee = 0.00669342162296594323;
            double dLat = TransformLat(lon - 105.0, lat - 35.0);
            double dLon = TransformLon(lon - 105.0, lat - 35.0);
            double radLat = lat / 180.0 * Math.PI;
            double magic = Math.Sin(radLat);
            magic = 1 - ee * magic * magic;
            double sqrtMagic = Math.Sqrt(magic);
            dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * Math.PI);
            dLon = (dLon * 180.0) / (a / sqrtMagic * Math.Cos(radLat) * Math.PI);
            return (dLat, dLon);
        }

        private static double TransformLat(double x, double y)
        {
            double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Math.PI) + 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(y * Math.PI) + 40.0 * Math.Sin(y / 3.0 * Math.PI)) * 2.0 / 3.0;
            ret += (160.0 * Math.Sin(y / 12.0 * Math.PI) + 320.0 * Math.Sin(y * Math.PI / 30.0)) * 2.0 / 3.0;
            return ret;
        }

        private static double TransformLon(double x, double y)
        {
            double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.Sqrt(Math.Abs(x));
            ret += (20.0 * Math.Sin(6.0 * x * Math.PI) + 20.0 * Math.Sin(2.0 * x * Math.PI)) * 2.0 / 3.0;
            ret += (20.0 * Math.Sin(x * Math.PI) + 40.0 * Math.Sin(x / 3.0 * Math.PI)) * 2.0 / 3.0;
            ret += (150.0 * Math.Sin(x / 12.0 * Math.PI) + 300.0 * Math.Sin(x / 30.0 * Math.PI)) * 2.0 / 3.0;
            return ret;
        }
    }

    // 输入坐标类型
    public record Coordinate(
        [property: Description("纬度")] double Lat,
        [property: Description("经度")] double Lon);

    // WeatherAPI 数据模型（Json 字段名映射）
    public record WeatherData(
        [property: JsonPropertyName("current")] Current Current,
        [property: JsonPropertyName("forecast")] Forecast Forecast);

    public record Current(
        [property: JsonPropertyName("temp_c")] double TempC);

    public record Forecast(
        [property: JsonPropertyName("forecastday")] ForecastDay[] Forecastday);

    public record ForecastDay(
        [property: JsonPropertyName("date")] DateTime Date,
        [property: JsonPropertyName("day")] DayData Day);

    public record DayData(
        [property: JsonPropertyName("maxtemp_c")] double MaxTempC,
        [property: JsonPropertyName("mintemp_c")] double MinTempC);

    // Open-Meteo 响应模型（无密钥）
    public record OpenMeteoResponse(
        [property: JsonPropertyName("current")] OpenMeteoCurrent? Current,
        [property: JsonPropertyName("daily")] OpenMeteoDaily? Daily);

    public record OpenMeteoCurrent(
        [property: JsonPropertyName("temperature_2m")] double? TemperatureC);

    public record OpenMeteoDaily(
        [property: JsonPropertyName("time")] string[] Time,
        [property: JsonPropertyName("temperature_2m_max")] double[] TMax,
        [property: JsonPropertyName("temperature_2m_min")] double[] TMin);

    // QWeather 响应模型
    public record QwNowResponse(
        [property: JsonPropertyName("code")] string Code,
        [property: JsonPropertyName("now")] QwNow? Now);

    public record QwNow(
        [property: JsonPropertyName("temp")] string? Temp,
        [property: JsonPropertyName("obsTime")] string? ObsTime);

    public record Qw3dResponse(
        [property: JsonPropertyName("code")] string Code,
        [property: JsonPropertyName("daily")] QwDaily[]? Daily);

    public record QwDaily(
        [property: JsonPropertyName("fxDate")] string FxDate,
        [property: JsonPropertyName("tempMax")] string TempMax,
        [property: JsonPropertyName("tempMin")] string TempMin);

    // Baidu 地理编码响应模型
    public record BaiduGeoResponse(
        [property: JsonPropertyName("status")] int Status,
        [property: JsonPropertyName("result")] BaiduGeoResult? Result);

    public record BaiduGeoResult(
        [property: JsonPropertyName("location")] BaiduLocation? Location,
        [property: JsonPropertyName("precise")] int Precise);

    public record BaiduLocation(
        [property: JsonPropertyName("lng")] double Lng,
        [property: JsonPropertyName("lat")] double Lat);
}
